
/************************** item_29: 为 "异常安全" 而努力的值得的 ********************************/

// "异常安全" 有两个条件：
// 当异常被抛出时，带有异常安全性的函数会：
// 1. 不泄露任何资源
// 2. 不允许数据败坏

// 异常安全函数提供以下三个保证之一：
// 1. 基本承诺：如果异常被抛出，程序内的任何事物仍然保持在有效状态下。没有任何对象或数据结构会因此而败坏，所有对象都处于一种内部前后一致的状态（例如
// 所有的 class 约束条件都继续获得满足）。然而程序的现实状态恐怕不可预料。举个例子，我们可以撰写 changeBackground 使得一旦有异常被抛出时，PrettyMenu
// 对象可以继续拥有原背景图像，或是令拥有某个缺省背景图像，但客户无法预料哪一种情况。如果想知道，他们恐怕必须调用某个成员函数以得知当时的背景图像的什么。
// 2. 强烈保证：如果异常被抛出，程序状态不改变。调用这样的函数需要有这样的认知：如果函数成功，就是完全成功，如果函数是失败，程序会回复到"调用函数之前"的状态。
// 和这种强烈保证的函数共事，比如刚才说的那种只提供基本承诺的函数共事，容易多了，因为在调用一个强烈保证的函数后，程序状态只有两种可能：如预期般地到达函数
// 成功执行后的状态，或回到函数被调用前的状态。与此成对比的是，如果调用一个只提供基本承诺的函数，而真的出现异常，程序有可能处于任何状态 --- 只要那是个合法状态。
// 3. 不抛掷保证：承诺绝不抛出异常，因为它们总是能够完成它们原先承诺的功能。作用于内置类型（例如 ints，指针等等）身上的所有操作都提供 nothrow 保证。
// 这是异常安全码中一个必不可少的关键基础材料。

#include <istream>
#include <mutex>

class PrettyMenu {
public:
    // ...
    void changeBackground(std::istream& imgSrc);    // 改变背景图像
    // ...
private:
    std::mutex mutex;   // 互斥器
    Image* bgImage;     // 目前的背景图像
    int imageChanges;   // 背景图像被改变的次数
};

// 下面是 PrettyMenu 的changeBackground函数的一个可能实现：
void PrettyMenu::changeBackground(std::istream& imgSrc) {
    lock(&mutex);                   //取得互斥器
    delete bgImage;                 //摆脱旧的背景图像
    ++imageChanges;                 //修改图像变更次数
    bgImage = new Image(imgSrc);    //安装新的背景图像
    unlock(&mutex);                 //释放互斥器
}

void PrettyMenu::changeBackground(std::istream& imgSrc) {
    Lock ml(&mutex);                //来自条款14:获取互斥器并确保它稍后被释放
    delete bgImage;                 //摆脱旧的背景图像
    ++imageChanges;                 //修改图像变更次数
    bgImage = new Image(imgSrc);    //安装新的背景图像
}

// 将 Image* 的内置指针改变为一个 "用于资源管理" 的智能指针
class PrettyMenu {
public:
    // ...
    std::shared_ptr<Image> bgImage;
    // ...
};

// 不要为了表示某件事情发生而改变对象状态，除非那件事情真的发生了。
void PrettyMenu::changeBackground(std::istream& imgSrc) {
    Lock ml(&mutex);                    //来自条款14:获取互斥器并确保它稍后被释放
    bgImage.reset(new Image(imgSrc));   //以 "new Image" 的执行结果设定bgImage内部指针

    ++imageChanges;                     //修改图像变更次数
}

// copy and swap 策略：为你打算修改的对象（原件）做出一份副本，然后在那副本身上做一切必要的修改。若有任何修改动作抛出异常，原对象仍保持未改变状态。
// 待所有改变成功后，再将修改过的那个副本和原对象在一个不抛出异常的操作中置换。
// 实现上通常是将所有 "隶属对象的数据" 从原对象放进另一个对象内，然后赋予原对象一个指针，指向那个所谓的实现对象（即副本）。这种手法常被称为 pimpl idiom，
// 典型写法如下：
struct PMImpl {                         //PMImpl = "PrettyMenu Impl"
    std::shared_ptr<Image> bgImage;
    int imageChanges;
};

class PrettyMenu {

    // ...

private:
    std::mutex mutex;   // 互斥器
    std::shared_ptr<PMImpl> pImpl;
};

void PrettyMenu::changeBackground(std::istream& imgSrc) {
    using std::swap;
    Lock ml(&mutex);                 //获得mutex的副本数据
    std::shared_ptr<PMImpl> pNew(new PMImpl(*pImpl));
    pNew->bgImage.reset(new Image(imgSrc)); //修改副本
    ++pNew->imageChanges;
    swap(pImpl, pNew);                      //置换（swap）数据，释放mutex
}

// 总结：
// 1. 异常安全函数即使发生异常也不会泄露资源或允许任何数据结构败坏。这样的函数区分为三种可能的保证：基本型、强烈型、不抛出异常型。
// 2. "强烈保证" 往往能够以 copy-and-swap 实现出来，但 "强烈保证" 并非对所有函数都可实现或具备现实意义。
// 3. 函数提供的 "异常安全保证" 通常最高只等于其所调用之各个函数的 "异常安全保证" 中的最弱者。